package coherence;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import coherence.multiarray.MultiArray;
import coherence.multiarray.RecursiveAccess;



public class CoherenceModule implements CoherenceUpdater{
	
	private static Logger logger = Logger.getLogger("edu.hawaii.ccpv");

	static{
		ConsoleHandler ch = new ConsoleHandler();
		logger.addHandler(ch);
        // Request that every detail gets logged.
        logger.setLevel(Level.ALL);

	}

	private MultiArray<MultiArray<Float>> altruisms;		//an altruism of each cross section towards
															//every other cross section
	private MultiArray<MultiArray<Float>> newAltruisms;		//the coherence updated altruisms
	private MultiArray<Float> powerByCrossSection;			//the power of each cross section
	private List<List<int[]>> partition;					//the list of cross sections in each coalition
															//each int[] denotes a cross section
	private boolean[][] won_i_j;							//whether coalition i beat coalition j or vice-versa
	private MultiArray<Float> populationByCrossSection;		//the population of each cross section
	
	private float lambda = 0.5f;							//parameter representing percenatge of opponets assets
															//taken in victory
	private float c = 0.5f;									//parameter representing dead-weight loss of conflict
															//as a percentage of assets
	//private float I = 0.5f;
	private float epsilon = 0.1f;							//parameter representing randomness added to each
															//bilateral conflict's outcome
	
	/*
	 * index legend:  i and j span over the indices of the coalitions with i representing a
	 * source coalition and j a target coalition.
	 * n spans over a source coalition's cross sections, and j spans over a target
	 * coalition's cross sections 
	 */
	
	
	private int numCoalitions;
	private float[] sigma_i;		//sigma_i[i]
	private float[][] sigma_i_n;	//sigma_i_n[i][n]
	private int[] coalitionSizes;	//coalitionSizes[i]
	private float[][] theta_i_j;	//theta_i_j[i][j]
	private float[][][] tau_i_j_n;	//tau_i_j_n[i][j][n]
	private float[] pi_i;			//pi_i[i]
	
	float[][][][] betas;			//betas[i][j][n][m]
	float[][][][] newBetas;			//newBetas[i][j][n][m]
	float[][] averagedBetas;		//averagedBetas[i][j]
	float[][][] freeRideWinningProb;//freeRideWinningProb[i][j][n]
	float[][] contribWinningProb;	//contribWinningProb[i][j]
	
	float[][] sumBetas_Own;			//sumBetas_Own[i][n]
	float[][][] sumBetas_Other;		//sumBetas_Other[i][j][n]

	
	public static CoherenceUpdater getCoherenceUpdater(CoherenceBuilder coherenceBuilder) {
		return new CoherenceModule(coherenceBuilder);
	}
	
	public CoherenceModule(CoherenceBuilder coherenceBuilder) {
		super();
		
		CoherenceInputsFromConflict conflictInputs = coherenceBuilder.getCoherenceInputsFromConflict();
		CoherenceInputsFromGlobals globalsInputs = coherenceBuilder.getCoherenceInputsFromGlobals();
		
		this.altruisms = conflictInputs.altruisms;
		this.powerByCrossSection = conflictInputs.powerByCrossSection;
		this.populationByCrossSection = conflictInputs.populationByCrossSection;
		this.partition = conflictInputs.partition;
		this.theta_i_j = conflictInputs.theta_i_j;
		this.won_i_j = conflictInputs.won_i_j;
		
		this.lambda = globalsInputs.lambda;
		this.c = globalsInputs.c;
		//this.I = globalsInputs.I;
		this.epsilon = globalsInputs.epsilon;
		
		//this.coalitionSizes = coalitionSizes;
		//this.numCoalitions = numCoalitions;
		//this.sigma_i = sigma_i;
		//this.theta_i_j = theta_i_j;
		//this.v_i_j = v_i_j;
		
		computeCoalitionSizes();
		computeSigmasAndPis();
		computeTaus();
		computeContribWinningProbs();
		computeFreeRideWinningProbs();
		computeBetas();
		//computeSumBetas_i();
		//computeSumBetas_j();
		computeSumBetas_Own();
		computeSumBetas_Other();
		computeNewBetas();
		newAltruisms = GetNewAltruismsFromBetas();
		System.out.println("**********old betas");
		String oldBetasString = print2DArrayOf2DArrays(betas);
		System.out.println(oldBetasString);
		System.out.println("**********new betas");
		String newBetasString = print2DArrayOf2DArrays(newBetas);
		System.out.println(newBetasString);
		float[][][][] gammas = computeGammas(newBetas, betas);
		System.out.println("**********gammas");
		String gammaString = print2DArrayOf2DArrays(gammas);
		System.out.println(gammaString);
	}



	public static void main(String[] args) {
		
	}
	
	private void computeCoalitionSizes(){
		this.numCoalitions = partition.size();
		coalitionSizes = new int[numCoalitions];
		System.out.println("Number of coalitions: " + numCoalitions);
		logger.fine("Number of coalitions: " + numCoalitions);
		for(int i = 0; i < numCoalitions; i++){
			coalitionSizes[i] = partition.get(i).size();
			System.out.println("Coalition size " + i + " : " + coalitionSizes[i]);
		}
	}
	
	private void computeSigmasAndPis(){
		numCoalitions = partition.size();
		sigma_i = new float[numCoalitions];
		sigma_i_n = new float[numCoalitions][];
		pi_i = new float[numCoalitions];
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition = partition.get(i);
			int n = 0;
			sigma_i_n[i] = new float[coalitionSizes[i]];
			for(int [] crossSection : coalition){
				float crossSectionPopulation = populationByCrossSection.getElementAtIndex(crossSection);
				sigma_i[i] += crossSectionPopulation;
				sigma_i_n[i][n] = crossSectionPopulation;
				pi_i[i] += crossSectionPopulation * powerByCrossSection.getElementIndex(crossSection);
				n++;
			}
			pi_i[i] /= sigma_i[i];
		}
	}
	
	private void computeBetas(){
		
		betas = new float[numCoalitions][numCoalitions][][];
		averagedBetas = new float[numCoalitions][numCoalitions];
		
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition_i = partition.get(i);
			for(int j = 0; j < numCoalitions; j++){
				List<int []> coalition_j = partition.get(j);
				
				float[][] result = new float[coalitionSizes[i]][coalitionSizes[j]];
				
				float sourceTotalPopulation = 0;
				float sourceTotalAltruism = 0;
				
				float[] targetAveragedAltruisms = new float[coalitionSizes[i]];
				
				for(int n = 0; n < coalitionSizes[i]; n++){
					float targetTotalAltruism = 0;
					float targetTotalPopulation = 0;
					for(int m = 0; m < coalitionSizes[j]; m++){
						result[n][m] = altruisms.
						getElementAtIndex(coalition_i.get(n)).
						getElementAtIndex(coalition_j.get(m));
						float targetPopulation = populationByCrossSection.
						getElementAtIndex(coalition_j.get(m));
						targetTotalAltruism += result[n][m] * targetPopulation;
						targetTotalPopulation += targetPopulation;
					}
					if(targetTotalPopulation != 0)
						targetAveragedAltruisms[n] = targetTotalAltruism/targetTotalPopulation;
					float sourcePopulation = populationByCrossSection.
					getElementAtIndex(coalition_i.get(n));
					sourceTotalAltruism += targetAveragedAltruisms[n] * sourcePopulation;
					sourceTotalPopulation += sourcePopulation;
				}		
				betas[i][j] = result;
				if(sourceTotalPopulation != 0)
					averagedBetas[i][j] = sourceTotalAltruism/sourceTotalPopulation;
			}
		}
	}
	
	private void computeFreeRideWinningProbs(){
		freeRideWinningProb = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++)
			for(int j = 0; j < numCoalitions; j++){
				float[] free_i_j = new float[coalitionSizes[i]];
				for(int n = 0; n < coalitionSizes[i]; n++){
					float numerator = (theta_i_j[i][j] - tau_i_j_n[i][j][n]);
					float denominator = (epsilon + theta_i_j[i][j] - tau_i_j_n[i][j][n] + theta_i_j[j][i]);
					if(numerator == 0 && denominator == 0) 
						free_i_j[n] = 0.5f;
					else
						free_i_j[n] = numerator/denominator;
				}
				freeRideWinningProb[i][j] = free_i_j;
			}
	}
	
	private void computeContribWinningProbs(){
		contribWinningProb = new float[numCoalitions][numCoalitions];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				float numerator = theta_i_j[i][j];
				float denominator = (epsilon + theta_i_j[i][j] + theta_i_j[j][i]);
				if(numerator == 0 && denominator == 0) 
					contribWinningProb[i][j] = 0.5f;
				else
					contribWinningProb[i][j] = numerator/denominator;
			}
		}
	}
	
	//compute sum of beta's in opposing coalition
	private void computeSumBetas_Other(){
		//there is an "other" beta for each cross section in each coalition 
		//towards each other coalition
		//the question is, in computing the "other beta" for a given cross section
		//in a coalition, do we add the contributions of betas towards
		//a cross section's own coalition; the answer is no as seen below
		sumBetas_Other = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				
				if(i == j) continue; 	//do not add betas towards cross section's own coalition
				
				sumBetas_Other[i][j] = new float[coalitionSizes[i]];
				for(int n = 0; n < coalitionSizes[i]; n++){
					//notice that the value is the same for all m's 
					for(int m = 0; m < coalitionSizes[j]; m++){
						//weight the contribution of each beta 
						//by the target cross section's population
						sumBetas_Other[i][j][n] += betas[i][j][n][m]*sigma_i_n[j][m]; 
					}
				}
			}
		}
	}
	
	//compute sum of beta's in own coalition
	private void computeSumBetas_Own(){
		//there is an "own" beta for each cube in each coalition
		//the question here is whether we add a beta contribution of a cross section's
		//altruism towards itself, and the answer here is in the affirmative
		sumBetas_Own = new float[numCoalitions][];
		for(int i = 0; i < numCoalitions; i++){
			sumBetas_Own[i] = new float[coalitionSizes[i]];
				for(int n = 0; n < coalitionSizes[i]; n++){
					for(int m = 0; m < coalitionSizes[i]; m++){
						//weight the contribution of each beta 
						//by the target cross section's population
						sumBetas_Own[i][n] += betas[i][i][n][m]*sigma_i_n[i][n]; 
					}
				}
		}		
	}	
	
	private void computeNewBetas(){
		newBetas = new float[numCoalitions][numCoalitions][][];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				//this is a full square matrix traversal and not just a bottom triangle
				//or top triangle traversal, since for each bilateral conflict, their are
				//altruism updates occurring on each side of the conflict
				
				newBetas[i][j] = new float[coalitionSizes[i]][coalitionSizes[j]];
				if(i == j) continue;
				if(won_i_j[i][j]){
					contributeWinUpdate(i, j);
				} else {
					contributeLoseUpdate(i, j);		
				}
			}
		}
		
	}
	
	//equation (7) of Prof. Chai's Coherence Model document
	private void contributeLoseUpdate(int i, int j){
		for(int n = 0; n < coalitionSizes[i]; n++){
			for(int m = 0; m < coalitionSizes[j]; m++){
				if(sigma_i[j] > 0 || sigma_i_n[i][n] > 0)
					newBetas[i][j][n][m] = 
						betas[i][j][n][m] - sumBetas_Other[i][j][n]/sigma_i[j] - c/lambda;
				else 
					newBetas[i][j][n][m] = betas[i][j][n][m];
			}
		}
	}
	
	//equation (13) of Prof. Chai's Coherence Model document
	private void contributeWinUpdate(int i, int j){
		//System.out.println("called contributeWinUpdate");
		
		if(sigma_i[i] == 0 || sigma_i[j] == 0){
			for(int n = 0; n < coalitionSizes[i]; n++){
				for(int m = 0; m < coalitionSizes[j]; m++){
					newBetas[i][j][n][m] = betas[i][j][n][m];
				}
			}
			return;
		}
		
		for(int n = 0; n < coalitionSizes[i]; n++){
			
			if(sigma_i_n[i][n] == 0){
				for(int m = 0; m < coalitionSizes[j]; m++){
					newBetas[i][j][n][m] = betas[i][j][n][m];
				}
			}
			
			//float oldBeta = betas[i][j][m][n];
			float term1 = pi_i[i]*contribWinningProb[j][i] - 
				(pi_i[i] + tau_i_j_n[i][j][n])*freeRideWinningProb[i][j][n];
			float term2 = pi_i[j]*contribWinningProb[i][j] -
				pi_i[j]*freeRideWinningProb[i][j][n];
			float term3 = 2*pi_i[i]*contribWinningProb[i][j] - 
				(2*pi_i[j] + tau_i_j_n[i][j][n])* freeRideWinningProb[i][j][n];
			//float gamma_i_j_n = ((sumBeta_j_s[i][j][m]/sigma_i[j])*term1-
			//(sumBeta_i_s[i][j][n]/sigma_i[i])*term2 + c*tau_i_j_n[i][j][n]/lambda)/
			//term3;
			float sumBetasOwn = sumBetas_Own[i][n];
			float sumBetasOther = sumBetas_Other[i][j][n];
			float gamma_i_j_n = 0;
			//if(sigma_i[i] != 0 && sigma_i[j] != 0)
				gamma_i_j_n = ((sumBetasOther/sigma_i[j])*term1-
					(sumBetasOwn/sigma_i[i])*term2 + c*tau_i_j_n[i][j][n]/lambda)/
					term3;				
			for(int m = 0; m < coalitionSizes[j]; m++){
				newBetas[i][j][n][m] = betas[i][j][n][m] + gamma_i_j_n;
			}
			
		}
	}
	
	private MultiArray<MultiArray<Float>> GetNewAltruismsFromBetas(){
		
		int[] dimensions = altruisms.getDimensions();
		int[] indices = new int[dimensions.length];
		//expand on all indices
		for(int i = 0; i < dimensions.length; i++) indices[i] = -1;
		MultiArray<MultiArray<Float>> result = new MultiArray<MultiArray<Float>>(dimensions);		
		List<int[]> permutations = RecursiveAccess.generateAllPermutations(indices, dimensions);
		
		for(int[] permutation : permutations){	
			MultiArray<Float> inner = new MultiArray<Float>(dimensions);
			result.setElementAtIndex(permutation, inner);
		}
		
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition_i = partition.get(i);
			for(int j = 0; j < numCoalitions; j++){
				List<int []> coalition_j = partition.get(j);
				
				float[][] betas_i_j = newBetas[i][j];
				
				for(int n = 0; n < coalitionSizes[i]; n++){
					for(int m = 0; m < coalitionSizes[j]; m++){
						//we don't need the old value since the new values take it into account
						//float oldValue = altruisms.getElementAtIndex(coalition_i.get(n)).
						//getElementAtIndex(coalition_j.get(m));
						float newValue = betas_i_j[n][m];
						result.
						getElementAtIndex(coalition_i.get(n)).
						setElementAtIndex(coalition_j.get(m), newValue);
					}
				}		
			}
		}	
		
		return result;
			
	}
	
	private void computeTaus(){
		//there is a tau for each cross section in each coalition towards each other coalition
		float[][][] result = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++)
			for(int j = 0; j < numCoalitions; j++){
				result[i][j] = new float[coalitionSizes[i]];
				//all cross-sections in the i-th coalition
				//contribute the same resources towards conflict with
				//the jth coalition
				if(sigma_i[i] != 0)
					for(int n = 0; n < coalitionSizes[i]; n++)
						//result[i][j][n] = theta_i_j[i][j]*(sigma_i_n[i][n]/sigma_i[i]);
						result[i][j][n] = theta_i_j[i][j]/sigma_i[i];
			}
		tau_i_j_n = result;
	}	

	@Override
	public MultiArray<MultiArray<Float>> getNewAltruisms(
			MultiArray<MultiArray<Float>> altruisms) {
		return newAltruisms;
	}	
	
	public float[][][][] computeGammas(float[][][][] newBetas, float[][][][] betas){
		
		float[][][][] result = new float[numCoalitions][numCoalitions][][];

		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				float[][] gammas_i_j = new float[coalitionSizes[i]][coalitionSizes[j]];
				float[][] betas_i_j = betas[i][j];
				float[][] newBetas_i_j = newBetas[i][j];
				
				for(int n = 0; n < coalitionSizes[i]; n++){
					for(int m = 0; m < coalitionSizes[j]; m++){
						gammas_i_j[n][m] = newBetas_i_j[n][m] - betas_i_j[n][m];
					}
					
				}
				
				result[i][j] = gammas_i_j;
			}
		}
		return result;
	}
	
	public String print2DArrayOf2DArrays(float[][][][] betas){
		StringBuffer result = new StringBuffer("");

		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				float[][] betas_i_j = betas[i][j];
				result.append("i = " + i + " ; j = " + j + "\n");
				for(int m = 0; m < coalitionSizes[i]; m++){
					result.append("\t");
					for(int n = 0; n < coalitionSizes[j]; n++){
						result.append(betas_i_j[m][n] + "\t");
					}
					result.append("\n");
				}
			}
		}		
		
		return result.toString();
	}
	
}
